﻿using DynamicAuthorization.Sdk.Abstarctions;
using DynamicAuthorization.Sdk.Exceptions;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
using System.Runtime.Loader;

namespace DynamicAuthorization.Sdk.Impl;

public class GrantTypeMetadata : IGrantTypeMetadata
{
    private static readonly IEnumerable<Type> _typeCache = null!;
    private static readonly bool _isInit = false;
    private static readonly object _syncRoot = new();

    private protected static Assembly[] _loadAssemblies = Array.Empty<Assembly>();

    public bool IsAdmin { get; private set; } = false;

    static GrantTypeMetadata()
    {
        _loadAssemblies = AppDomain.CurrentDomain.GetAssemblies();

        if (!_isInit)
        {
            lock (_syncRoot)
            {
                if (!_isInit)
                {
                    _typeCache = _loadAssemblies
                        .Where(e => e.GetName().Name != "Anonymously Hosted DynamicMethods Assembly")
                        .SelectMany(x => AssemblyLoadContext.Default.LoadFromAssemblyName(x.GetName()).ExportedTypes)
                        .Where(n => n.Name.EndsWith("Controller"));

                    _isInit = true;
                }
            }
        }
    }

    public static IGrantTypeMetadata Empty { get; } = new GrantTypeMetadata();

    public ICollection<Type> GrantClasses { get; private set; } = new HashSet<Type>();

    public ICollection<Type> RejectClasses { get; private set; } = new HashSet<Type>();

    public IDictionary<Type, List<MethodInfo>> GrantMethods { get; private set; } = new Dictionary<Type, List<MethodInfo>>();

    public IDictionary<Type, List<MethodInfo>> RejectMethods { get; private set; } = new Dictionary<Type, List<MethodInfo>>();

    private GrantTypeMetadata()
    { }

    internal GrantTypeMetadata(string grant)
    {
        var targets = grant.Split(' ', StringSplitOptions.RemoveEmptyEntries);

        if (targets.Any(n => n == "#"))
        {
            IsAdmin = true;
        }

        var classPermission = targets.Where(n => !n.Contains(':') && n != "#");
        var methodPermission = targets.Where(n => n != "#").Except(classPermission);

        foreach (var item in classPermission)
        {
            if (item.StartsWith("~"))
            {
                var @class = _typeCache.FirstOrDefault(n => n.Name == $"{item[1..]}Controller");

                if (@class is not null)
                {
                    RejectClasses.Add(@class);
                }
            }
            else
            {
                var @class = _typeCache.FirstOrDefault(n => n.Name == $"{item}Controller");

                if (@class is not null)
                {
                    GrantClasses.Add(@class);
                }
            }
        }

        foreach (var item in methodPermission)
        {
            var isReject = item.StartsWith("~");

            var temp = item.Split(':', StringSplitOptions.RemoveEmptyEntries);
            var methodWithParms = temp[1].Split('(', StringSplitOptions.RemoveEmptyEntries);
            var methodName = methodWithParms[0];
            var parms = methodWithParms[1][..^1].Split(',', StringSplitOptions.RemoveEmptyEntries).Select(n => n.Trim());

            var @class = _typeCache.FirstOrDefault(n => n.Name == $"{(isReject ? temp[0][1..] : temp[0])}Controller");

            if (@class is null || GrantClasses.Contains(@class))
            {
                continue;
            }

            if (isReject)
            {
                RejectMethods.TryAdd(@class, new List<MethodInfo>());

                var methods = @class
                    .GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .Where(n => n.Name == methodName)
                    .Where(n => n.GetParameters().Select(x => x.ParameterType.Name).SequenceEqual(parms))
                    .ToList();

                if (methods is null || !methods.Any())
                {
                    continue;
                }

                RejectMethods[@class].AddRange(methods);
            }
            else
            {
                GrantMethods.TryAdd(@class, new List<MethodInfo>());

                var methods = @class
                    .GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .Where(n => n.Name == methodName)
                    .Where(n => n.GetParameters().Select(x => x.ParameterType.Name).SequenceEqual(parms))
                    .ToList();

                if (methods is null || !methods.Any())
                {
                    continue;
                }

                GrantMethods[@class].AddRange(methods);
            }
        }
    }

    public GrantTypeMetadata(Assembly assembly)
    {
        var controllers = assembly.ExportedTypes.Where(t => t.IsClass && !t.IsAbstract && typeof(ControllerBase).IsAssignableFrom(t)).ToList();

        GrantClasses = new HashSet<Type>(controllers);

        foreach (var item in controllers)
        {
            var methods = item.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            GrantMethods.Add(item, methods.ToList());
        }
    }

    public GrantTypeMetadata(IEnumerable<Type> typeNames)
    {

    }

    public Type? MapClass(Type controller)
    {
        var isReject = RejectClasses.Any(n => n == controller);

        if (isReject)
        {
            throw new RejectedAccessException("未持有该模块的访问权限");
        }

        var result = GrantClasses.FirstOrDefault(n => n == controller);

        return result is null ? null : result;
    }

    public MethodInfo? MapMethod(Type controller, MethodInfo action)
    {
        var rejectMethods = RejectMethods.FirstOrDefault(n => n.Key == controller).Value;

        if (rejectMethods is not null && rejectMethods.Any())
        {
            if (rejectMethods.Any(n => n == action))
            {
                return null;
            }
        }

        var methods = GrantMethods.FirstOrDefault(n => n.Key == controller).Value;

        if (methods is null || !methods.Any())
        {
            return null;
        }

        var result = methods.FirstOrDefault(n => n == action);

        return result is null ? null : result;
    }
}